-- DungeonLogM
-- Create by zouyb
-- 地牢日志

module("DungeonLogM", package.seeall);

-- 地牢日志记录
local dungeonLog = {};

-- 玩家数据备份
local userData = {};

local onMsgDone = nil;

function init()
    -- 关注登录事件
    EventMgr.removeAll("DungeonLogM");
    EventMgr.register("DungeonLogM", event.LOGIN_OK, function()
        -- 清除记录
        userData = {};
        dungeonLog = {};
    end);

    EventMgr.register("DungeonLogM", event.MESSAGE_DONE, onMsgDone)
end

local function getKey(dungeonId, layer)
	return tostring(dungeonId).."-"..tostring(layer);
end

-- 增加日志
function addLog(info)
    trace(info);
end

-- 获取日志
function getLog(dungeonId, layer)
	local key = getKey(dungeonId, layer);
	if dungeonLog[key] == nil then
	   return nil;
	end

    return table.append(dungeonLog[key], collectExtraInfo());
end

-- 获取压缩日志并清除
function popLog(dungeonId, layer)
    if not dungeonLog then
        return;
    end
    local data = table.values(dungeonLog)[1];

    dungeonLog = {};

    local msg = "";
    for index, info in pairs(data) do
        msg = msg .. info .. "\n";
    end

    local zlib = require "zlib";
    local buf = zlib.compress(msg);
    local b = Buffer.create(4);
    Buffer.setu32(b, 1, string.len(msg));
    Buffer.append(b, buf);

    print(string.format("压缩前:%d, 压缩后:%d", string.len(msg), string.len(buf)));

    return b;
end

-- 收集额外数据
function collectExtraInfo()
    -- 玩家出现异常时的网络环境（移动数据还是wifi）
    -- 玩家出现异常时的灵魂链接信息（记录class_id即可）
    -- 玩家出现异常时的装备数据（记录佩戴的装备class_id）
    -- 玩家出现异常时的称号信息（称号，称号等级）

    local ret = {};
    local network = iif(NetworkStatus and NetworkStatus.isWifiConnected(), "wifi", "mobile");

    -- 1. 网络
    table.insert(ret, "network : " .. network);

    -- 2. 灵魂链接
    table.insert(ret, "lictor : " .. tableToString(ME.user.dbase:query("lictor_pets", {})));

    return ret;
end

-- 清除日志
function clearLog(dungeonId, layer)
	local key = getKey(dungeonId, layer);
    dungeonLog[key] = {};
end

-- 记录开始迷宫时的状态
-- 1. 记录当前的基础属性：hp、max_hp、mp、max_mp、attack、magic、search_point
-- 2. 当前技能+天赋
-- 3. 附加属性（格式需要特殊处理压缩一下）
-- 4. 当前迷宫格子元素（包括钥匙位置）+随机数，包括当前所有的游标
-- 5. 装备+称号
function recordCurrentState()
    if is_trace_enable() then
        -- 1. 当期基础属性
        local str = collectBaseAttrib();

        str = str .. "\n";
        addLog(str);
    end
end

-- 基础属性
function collectBaseAttrib()
    local ret = "";
    ret = ret .. "hp:(" .. ME.user:queryAttrib("hp") .. "/" .. ME.user.dbase:query("hp", 0) .. ")";

    -- 最大血量
    ret = ret .. "max_hp:(" .. ME.user:queryAttrib("max_hp") .. "/" .. ME.user.dbase:query("max_hp", 0) .. ")";

    -- 魔法值
    ret = ret .. "mp:(" .. ME.user:queryAttrib("mp") .. "/" .. ME.user.dbase:query("mp", 0) .. ")";

    -- 最大魔法值
    ret = ret .. "max_mp:(" .. ME.user:queryAttrib("max_mp") .. "/" .. ME.user.dbase:query("max_mp", 0) .. ")";

    -- 攻击
    ret = ret .. "attack:(" .. ME.user:queryAttrib("attack") .. ")";

    -- 魔力
    ret = ret .. "magic:(" .. ME.user:queryAttrib("magic") .. ")";

    -- 探索点
    ret = ret .. "search_point:(" .. ME.user:queryAttrib("search_point") .. ")";

    return ret;
end

-- 收集技能天赋
function collectSkillAndTalents()
    local skills  = ME.user:getAllSkills();
    local talents = ME.user:getTalents();

    return "skills:" .. arrayToString(skills) .. "talents:" .. arrayToString(talents);
end

-- 收集附加属性
function collectProps()
    local props = ME.user.dbase:queryTemp("prop", {});
    local ret = "";
    for path, list in pairs(props) do
        ret = ret .. path .. ":";
        for _, prop in pairs(list) do
            ret = ret .. arrayToString(prop);
        end
    end

    return ret;
end

-- 收集当前格子元素（包括钥匙位置）
function collectDungeonGrids()
    -- 如果是副本、神龙夹层中的，就不用管了
    if DungeonInstanceM.isInInstance() or UnrealDungeonM.isInUnreal() then
        return "";
    end

    local dungeonData = DungeonM.getDungeonContainer();
    local layer = DungeonM.currentLayer();
    local grids = dungeonData.combats[layer];
    local ret = "grids:";

    for pos = 1, GRID_SIZE do
        local data = grids[pos];
        ret = ret .. table.values(data)[1] .. ",";
    end

    -- 钥匙位置
    ret = ret .. "key_pos:" .. dungeonData.keyPos[layer] .. "\n";

    -- 所有的随机数
    ret = ret .. "rand_seed:" .. arrayToString(dungeonData.rand_seed);

    -- 游标
    ret = ret .. "randomCursor:" .. tableToString(ME.user.dbase:query("randomCursor", {}));

    return ret;
end

-- 收集装备+称号
function collectEquipmentsAndCareer()
    -- 1. 装备
    local pet = ME.user:getActivePet();
    local ret = "user_equipments : " .. tableToString(ME.user.equipments or {}) .. "\n";
    ret = ret .. "pet_equipments:" .. tableToString(pet.equipments) .. "\n";

    -- 2. 称号
    ret = ret .. " careers : " .. tableToString(pet:getCareerData());

    return ret;
end

-- 召唤兽数据
function collectSummonDatas()
    local ret = "summon : " .. tableToString(ME.user.dbase:query("summon", {})) .. "\n";

    ret = ret .. "bine : " .. tableToString(ME.user.dbase:query("bine", {})) .. "\n";

    ret = ret .. "ghost_ship : " .. tableToString(ME.user.dbase:query("ghost_ship", {})) .. "\n";

    ret = ret .. "bine_addon : " .. tableToString(ME.user.dbase:query("bine_addon", {})) .. "\n";

    ret = ret .. "bine_attrib : " .. tostring(ME.user:queryAttrib("bine_attrib")) .. "\n";

    return ret;
end

function arrayToString(t)
    local ret = "";
    for index = 1, #t do
        ret = ret .. t[index] .. ",";
    end

    return ret;
end

function getUserData(layer)
    return userData[layer or DungeonM.currentLayer()];
end

function clearUserData(layer)
    if not layer then
        userData = {};
    else
        userData[layer] = nil;
    end
end

-- 过滤掉的字段
local drops = {
    dungeon_score_info = 1, monster_appear_log = 1, dungeon_progress = 1, relic_rob_list = 1, new_airships = 1,
    mop_up_info = 1, sky_instance_bonus = 1, learned_career_data = 1, skyArmyGroup = 1, relic_boss_info = 1,
    shopData = 1, market_buy = 1, shipSkills = 1, rare_slime_setting = 1, limit_time_gift = 1, sky_explore = 1,
    artifact_durability = 1, relic_combat_record = 1,
};

-- 收集整个玩家数据，暂时考虑收集所有数据
function collectEntireUser()
    local dbase = filterAndCopy(ME.user.dbase.dbase);

    for path, info in pairs(ME.user.dbase.dbase) do
        if drops[path] then
            dbase[path] = nil;
        end
    end

    dbase.rid = ME.user.rid;
    dbase.equipments = table.deepcopy(to_mapping(ME.user.equipments));
    dbase.skills = table.deepcopy(ME.user.skills);
    dbase.talents = table.deepcopy(ME.user.talents);

    local data = {["dbase"] = dbase, };

    -- 考虑到有些数据不是在dbase下的
    data.achievements     = table.deepcopy(to_mapping(ME.user.achievements));
    data.game_achievement = table.deepcopy(to_mapping(ME.user.game_achievement));
    data.exchangeTimes    = table.deepcopy(to_mapping(DungeonBankM.getExchangeTimes()));
    data.instance_stat    = table.deepcopy(to_mapping(ME.user.instance_stat));
    data.gridStat         = table.deepcopy(to_mapping(DungeonStatM.queryStat()));
    data.combatStat       = table.deepcopy(to_mapping(CombatStatM.packCombatStat()));

    -- 道具
    data.items = {__mapping = 1};
    for classId, item in pairs(ME.user.items) do
        data.items[classId] = item.amount;
    end

    -- 宠物数据
    data.pets = {__mapping = 1};
    for classId, pet in pairs(ME.user.pets) do
        data.pets[classId] = pet:getSaveData();
    end

    -- 行为记录
    data.actionRecord = filterAndCopy(ME.user.actionRecord);

    -- 记录下版本号
    data.client_version = CLIENT_VERSION or "null";

    -- 该层开始时的数据保存下来
    local layer = DungeonM.currentLayer();
    userData[layer] = data;

    -- 仅保留最近5层的数据
    if layer > 5 then
        userData[layer - 5] = nil;
    end
end

function collectFinalData(layer)
    -- 播放中就不用管了
    if StatusM.check(ME.user, "PLAYING_RECORD") then
        return;
    end

    -- 记录下来
    local data = getUserData(layer);
    if not data then
        return;
    end

    data.finalData = packImportantData();
end

function checkFinalData(layer)
    local currDAta = packImportantData();

    -- 不再播放中
    if not StatusM.check(ME.user, "PLAYING_RECORD") then
        return;
    end

    -- 录像中，对比下两次的属性
    local recordData = ME.user.finalData or {};
    local attrib1 = currDAta["attrib"] or {};
    local attrib2 = recordData["attrib"] or {};

    print("********************************************\n属性比较:");
    for key, _ in pairs(table.add(attrib1, attrib2)) do
        if attrib1[key] ~= attrib2[key] then
            print(string.format(">>>>>>>>>>>> attrib: %s 播放数量: %s 旧数量: %s", key, tostring(attrib1[key]), tostring(attrib2[key])));
        end
    end

    local items1 = currDAta["items"] or {};
    local items2 = recordData["items"] or {};
    print("********************************************\n道具比较:");
    for classId, _ in pairs(table.add(items1, items2)) do
        if items1[classId] ~= items2[classId] then
            print(string.format(">>>>>>>>>>>> classId: %s 播放数量: %s 旧数量: %s", classId, tostring(items1[classId]), tostring(items2[classId])));
        end
    end
end

-- 收集当前属性+道具数量
function packImportantData()
    -- 1. 道具
    local items = {};
    for classId, item in pairs(ME.user.items) do
        items[classId] = item.amount;
    end

    local attrib = table.add({
        ["hp"] = ME.user:queryAttrib("hp"),
        ["max_hp"] = ME.user:queryAttrib("max_hp"),
        ["mp"] = ME.user:queryAttrib("mp"),
        ["max_mp"] = ME.user:queryAttrib("max_mp"),
        ["attack"] = ME.user:queryAttrib("attack"),
        ["magic"]  = ME.user:queryAttrib("magic"),
        ["dodge"]  = ME.user:queryAttrib("dodge"),
        ["accuracy"] = ME.user:queryAttrib("accuracy"),
        ["search_point"] = ME.user:queryAttrib("search_point"),
    }, ME.user.dbase:query("dungeon_attrib", {}));

    return { ["items"] = items, ["attrib"] = attrib, };
end

-- 深度拷贝一个table，并过滤掉其中的clone类对象
-- 另外布尔变量也要过滤掉
function filterAndCopy(data)
    local varType = type(data);

    if "boolean" == varType then
        return iif(data, 1, 0);
    elseif "table" ~= varType then
        return data;
    end

    -- Array格式
    if data.array == true and data.e then
        data.e = filterAndCopy(data.e, filter);

        return data;
    end

    local ret = {};
    local arr = table.keys(data)
    for _, k in pairs(arr) do
        local v = data[k];

        repeat
            -- TODO 如果是clone类对象（暂时这样判断）
            if type(v) == "table" and v.__index then
                print("find illegal object: key = " .. k .. " value = " .. tostring(v));
                break;
            end
            if type(v) == "function" then
                print("find illegal function: key = " .. k);
                break;
            end

            ret[k] = filterAndCopy(v);
        until true;
    end

    return ret;
end

-- 行为记录
function addRecord(data)
    -- 如果是在播放中就不用管了
    if StatusM.check(ME.user, "PLAYING_RECORD") then
        return;
    end

    local layer = DungeonM.currentLayer();
    if not layer or not userData[layer] then
        return;
    end

    local record = userData[layer]["actionRecord"] or {};

    -- 加上当前的时间、迷宫状态
    data.time = os.time();
    data.dungeon_state = DungeonM.getState();

    table.insert(record, data);

    userData[layer]["actionRecord"] = record;
end

function stepPlay()
    -- 不是在播放中
    if not StatusM.check(ME.user, "PLAYING_RECORD") then
        return;
    end

    local record = ME.user.actionRecord or {};
    local index = ME.user.replayIndex or 1;

    if not record[index] then
        trace("DungeonLogM", "已经结束");

        checkFinalData();

        return;
    end

    local action = record[index];

    if action.done then
        local progress = ME.user.actionProgress;

        ME.user.actionProgress = progress + 1;
        print("******************* 回放进度 " .. progress + 1 .. "/" .. ME.user.actionSum .. " *******************");
    else
        print("******************* 无效action *******************");
    end

    ME.user.replayIndex = index + 1;

    if not record[index + 1] then
        checkFinalData();
    end

    print("\n\n\n---------------------------\n>>>>>>> action: " .. action.cmd .. " " .. tableToString(action));

    -- 迷宫行为
    DungeonActionM.go(action.cmd, action.pos, action.data, action.extra);

    EventMgr.fire(event.PLAYER_MOVE);

    return action;
end

onMsgDone = function(para)
    -- MSG_OBJECT_UPDATED比较多，过滤掉
    if para[1] == "MSG_OBJECT_UPDATED" then
        return;
    end

    local layer = DungeonM.currentLayer();
    if not layer or not userData[layer] then
        return;
    end

    local msg_list = userData[layer]["msg_list"] or {};

    -- 不要保存太多
    if sizeof(msg_list) > 100 then
        return;
    end

    -- 加上当前的时间、迷宫状态
    local data = {
        ["time"] = os.time(),
        ["cmd"] = para[1],
    };

    table.insert(msg_list, data);

    userData[layer]["msg_list"] = msg_list;
end
